use serde::Deserialize;
use std::{env, path::Path, sync::LazyLock};

use super::environment_mode_service;

// The [APP_CONFIG] we have defined as a static immutable variable using lazy_static is a perfectly reasonable approach,
// especially given that it's immutable and is only initialized once at startup.
pub static APP_CONFIG: LazyLock<AppConfig> = LazyLock::new(|| read_config());

#[derive(Debug, Deserialize)]
pub struct AppConfig {
    pub data_source: DataSource,
    pub jwt: Jwt,
    pub server: Server,
    pub resource: Resource,
}

#[derive(Debug, Deserialize)]
pub struct DataSource {
    pub url: String,
}

#[derive(Debug, Deserialize)]
pub struct Jwt {
    pub signing_key: String,
    pub expiration: u64,
    pub max_expiration: u64,
}

#[derive(Debug, Deserialize)]
pub struct Server {
    pub route_root_group_name: String,
    pub tcp_listener_bind_address: String,
    pub request_body_limit: usize,
    pub port: u16,
}

#[derive(Debug, Deserialize)]
pub struct Resource {
    pub upload: Upload,
    pub http_base_url: String,
    pub avatar_directory_name: String,
    pub default_avatar_url: String,
}

#[derive(Debug, Deserialize)]
pub struct Upload {
    pub root_file_path: String,
}

/// Reads local config files in config directory.
fn read_config() -> AppConfig {
    // Gets current executable path.
    let current_dir = env::current_dir().unwrap();
    let config_dirname = "config";

    // Prepres config filename based on current env mode.
    let config_filename = &format!(
        "application-{}.toml",
        environment_mode_service::CURRENT_ENV_MODE.current,
    );

    // Prepares config file full path for development environment.
    let config_file_path = current_dir
        .join(Path::new(config_dirname))
        .join(Path::new(config_filename));

    // Reads the config file
    let config_str = match std::fs::read_to_string(&config_file_path) {
        Ok(contents) => contents,
        Err(_) => {
            panic!("Failed to read config file '{:?}'", &config_file_path);
        }
    };

    // Deserializes the config string into a Config struct.
    let config: AppConfig = match toml::de::from_str(&config_str) {
        Ok(config) => config,
        Err(err) => {
            panic!("Failed to parse config file: {}", err);
        }
    };

    config
}
